package com.ktwlsoft.fundmanage.utils;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Description: Http请求连接池类
 *
 * @Author: Chen Cai
 * @Date: 2019/4/26 15:46
 */
@Component
@Slf4j
public class HttpPoolUtils {

    /** 日志对象 **/
    private static Logger logger = LoggerFactory.getLogger(HttpPoolUtils.class);

    // 连接建立的超时时间
    private final static Integer connectTimeout = 10000;
    // 连接超时时间
    private final static Integer socketTimeout = 10000;
    // 闲置超时时间
    private final static Integer idleTimeout = 5000;
    // 最大连接数
    private final static Integer maxConn = 200;
    // 路由默认最大连接数
    private final static Integer maxPreRoute = 100;
    // 路由最大连接数
    private final static Integer maxRoute = 100;

    /*@Value("${httpPool.connectTimeout}")
    public void setConnectTimeout(int connectTimeout) {
        HttpPoolUtils.connectTimeout = connectTimeout;
    }
    @Value("${httpPool.socketTimeout}")
    public void setSocketTimeout(int socketTimeout) {
        HttpPoolUtils.socketTimeout = socketTimeout;
    }
    @Value("${httpPool.idleTimeout}")
    public void setIdleTimeout(int idleTimeout) {
        HttpPoolUtils.idleTimeout = idleTimeout;
    }
    @Value("${httpPool.maxConn}")
    public void setMaxConn(int maxConn) {
        HttpPoolUtils.maxConn = maxConn;
    }
    @Value("${httpPool.maxPreRoute}")
    public void setMaxPreRoute(int maxPreRoute) {
        HttpPoolUtils.maxPreRoute = maxPreRoute;
    }
    @Value("${httpPool.maxRoute}")
    public void setMaxRoute(int maxRoute) {
        HttpPoolUtils.maxRoute = maxRoute;
    }*/

    // 发送请求的客户端单例
    private static CloseableHttpClient httpClient;
    //连接池管理类
    private static PoolingHttpClientConnectionManager manager;
    // 监控线程单例
    private static ScheduledExecutorService monitorExecutor;
    // 相当于线程锁,用于线程安全
//    private final static Object syncLock = new Object();

    /**
     * 对http请求进行基本设置
     * @param httpRequestBase http请求
     */
    private static void setRequestConfig(HttpRequestBase httpRequestBase){
        RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(connectTimeout)
                .setConnectTimeout(connectTimeout)
                .setSocketTimeout(socketTimeout).build();
        httpRequestBase.setConfig(requestConfig);
    }

    private static CloseableHttpClient getHttpClient(String url){
        String hostName = url.split("/")[2];
        System.out.println(hostName);
        int port = 80;
        if (hostName.contains(":")){
            String[] args = hostName.split(":");
            hostName = args[0];
            port = Integer.parseInt(args[1]);
        }

        if (httpClient == null){
            //多线程下多个线程同时调用getHttpClient容易导致重复创建httpClient对象的问题,所以加上了同步锁
//            synchronized (syncLock){
                if (httpClient == null){
                    httpClient = createHttpClient(hostName, port);
                    // 开启监控线程,对异常和空闲线程进行关闭
                    // monitorExecutor = Executors.newScheduledThreadPool(1);
                    monitorExecutor = new ScheduledThreadPoolExecutor(1);
                    monitorExecutor.scheduleAtFixedRate(new TimerTask() {
                        @Override
                        public void run() {
                            //关闭异常连接
                            manager.closeExpiredConnections();
                            //关闭5s空闲的连接
                            manager.closeIdleConnections(idleTimeout, TimeUnit.MILLISECONDS);
                            //log.info("close expired and idle for over 5s connection");
                        }
                    }, 0, 1000, TimeUnit.MILLISECONDS);
                }
//            }
        }
        return httpClient;
    }

    /**
     * 根据host和port构建httpclient实例
     * @param host 要访问的域名
     * @param port 要访问的端口
     * @return
     */
    private static CloseableHttpClient createHttpClient(String host, int port){
        ConnectionSocketFactory plainSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create().register("http", plainSocketFactory)
                .register("https", sslSocketFactory).build();

        manager = new PoolingHttpClientConnectionManager(registry);
        //设置连接参数
        manager.setMaxTotal(maxConn); // 最大连接数
        manager.setDefaultMaxPerRoute(maxPreRoute); // 默认路由最大连接数

        HttpHost httpHost = new HttpHost(host, port);
        manager.setMaxPerRoute(new HttpRoute(httpHost), maxRoute);

        //请求失败时,进行请求重试
        HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
                if (i > 3){
                    //重试超过3次,放弃请求
                    log.error("retry has more than 3 time, give up request");
                    return false;
                }
                if (e instanceof NoHttpResponseException){
                    //服务器没有响应,可能是服务器断开了连接,应该重试
                    log.error("receive no response from server, retry");
                    return true;
                }
                if (e instanceof SSLHandshakeException){
                    // SSL握手异常
                    log.error("SSL hand shake exception");
                    return false;
                }
                if (e instanceof InterruptedIOException){
                    //超时
                    log.error("InterruptedIOException");
                    return false;
                }
                if (e instanceof UnknownHostException){
                    // 服务器不可达
                    log.error("server host unknown");
                    return false;
                }
                if (e instanceof ConnectTimeoutException){
                    // 连接超时
                    log.error("Connection Time out");
                    return false;
                }
                if (e instanceof SSLException){
                    log.error("SSLException");
                    return false;
                }

                HttpClientContext context = HttpClientContext.adapt(httpContext);
                HttpRequest request = context.getRequest();
                if (!(request instanceof HttpEntityEnclosingRequest)){
                    //如果请求不是关闭连接的请求
                    return true;
                }
                return false;
            }
        };

        CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).setRetryHandler(handler).build();
        return client;
    }

    /**
     * 设置post请求的参数
     * @param httpPost
     * @param params
     */
    /*private static void setEntityParam(HttpEntityEnclosingRequestBase httpPost, Map<String, String> params){
        if(httpPost != null && params != null && params.size() > 0){
            List<NameValuePair> nvps = new ArrayList<NameValuePair>();
            Set<String> keys = params.keySet();
            for (String key: keys){
                nvps.add(new BasicNameValuePair(key, params.get(key)));
            }
            try {
                httpPost.setEntity(new UrlEncodedFormEntity(nvps, "utf-8"));
            } catch (UnsupportedEncodingException e) {
                logger.error(e.getMessage());
            }
        }
    }*/

    private static void setEntityParam(HttpEntityEnclosingRequestBase httpPost, Object params){
        if(httpPost != null && params != null){
            try {
                httpPost.setEntity(new StringEntity(JSONObject.toJSONString(params), "utf-8"));
            } catch (Exception e) {
                logger.error(e.getMessage());
            }
        }
    }

    /**
     * 设置头部信息
     * @param requst
     * @param headers
     */
    private static void setHeader(HttpRequestBase requst, Map<String, String> headers){
        if(requst != null && headers != null && headers.size() > 0){
            for (Map.Entry<String, String> entity : headers.entrySet()) {
                requst.setHeader(entity.getKey(), entity.getValue());
            }
        }
    }

    /**
     * 拼接参数
     * @param params
     * @return
     */
    private static String getUrlParams(Map<String, String> params){
        String url = "";
        if (params != null && params.size() > 0) {
            StringBuffer sb = new StringBuffer();
            for (Map.Entry<String, String> entity : params.entrySet()) {
                sb.append(entity.getKey() + "=" + entity.getValue() + "&");
            }
            String paramStr = sb.toString();
            url = "?" + paramStr.substring(0, paramStr.length() - 1);
        }
        return url;
    }

    /**
     * 通过get请求
     * @param url
     * @param headers（可不传）
     * @param params（可不传）
     * @return
     */
    public static String get(String url, Map<String, String> headers, Map<String, String> params){
        url += getUrlParams(params);
        HttpGet doRequest = new HttpGet(url);
        setRequestConfig(doRequest);
        setHeader(doRequest, headers);
        CloseableHttpResponse response = null;
        InputStream in = null;
        String result = null;
        try {
            response = getHttpClient(url).execute(doRequest, HttpClientContext.create());
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                result = IOUtils.toString(in, "utf-8");
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        } finally {
            try{
                if (in != null) {
                    in.close();
                }
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage());
            }
        }
        return result;
    }

    /**
     * 通过delete请求
     * @param url
     * @param headers（可不传）
     * @param params（可不传）
     * @return
     */
    public static String delete(String url, Map<String, String> headers, Map<String, String> params){
        url += getUrlParams(params);
        HttpDelete doRequest = new HttpDelete(url);
        setRequestConfig(doRequest);
        setHeader(doRequest, headers);
        CloseableHttpResponse response = null;
        InputStream in = null;
        String result = null;
        try {
            response = getHttpClient(url).execute(doRequest, HttpClientContext.create());
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                result = IOUtils.toString(in, "utf-8");
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        } finally {
            try{
                if (in != null) {
                    in.close();
                }
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage());
            }
        }
        return result;
    }

    /**
     * 通过post请求
     * @param url
     * @param headers（可不传）
     * @param params（可不传）
     * @return
     */
    public static String post(String url, Map<String, String> headers, Map<String, String> params){
        return post(url,headers,params,params);
    }

    /**
     * 通过post请求
     * @param url
     * @param headers（可不传）
     * @param params（可不传）
     * @return
     */
    public static String post(String url, Map<String, String> headers, Map<String, String> params, Object body){
        url += getUrlParams(params);
        HttpPost doRequest = new HttpPost(url);
        setRequestConfig(doRequest);
        setHeader(doRequest, headers);
        setEntityParam(doRequest, body);
        doRequest.setHeader("Content-type", "application/json");
        CloseableHttpResponse response = null;
        InputStream in = null;
        String result = null;
        try {
            response = getHttpClient(url).execute(doRequest, HttpClientContext.create());
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                result = IOUtils.toString(in, "utf-8");
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        } finally {
            try{
                if (in != null) {
                    in.close();
                }
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage());
            }
        }
        return result;
    }

    /**
     * 通过put请求
     * @param url
     * @param headers（可不传）
     * @param params（可不传）
     * @return
     */
    public static String put(String url, Map<String, String> headers, Map<String, String> params){
        return put(url,headers,params,params);
    }

    /**
     * 通过put请求
     * @param url
     * @param headers（可不传）
     * @param params（可不传）
     * @return
     */
    public static String put(String url, Map<String, String> headers, Map<String, String> params, Object body){
        url += getUrlParams(params);
        HttpPut doRequest = new HttpPut(url);
        setRequestConfig(doRequest);
        setHeader(doRequest, headers);
        setEntityParam(doRequest, body);
        doRequest.setHeader("Content-type", "application/json");
        CloseableHttpResponse response = null;
        InputStream in = null;
        String result = null;
        try {
            response = getHttpClient(url).execute(doRequest, HttpClientContext.create());
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                result = IOUtils.toString(in, "utf-8");
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        } finally {
            try{
                if (in != null) {
                    in.close();
                }
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage());
            }
        }
        return result;
    }

    /**
     * 通过patch请求
     * @param url
     * @param headers（可不传）
     * @param params（可不传）
     * @return
     */
    public static String patch(String url, Map<String, String> headers, Map<String, String> params, Object body){
        url += getUrlParams(params);
        HttpPatch doRequest = new HttpPatch(url);
        setRequestConfig(doRequest);
        setHeader(doRequest, headers);
        setEntityParam(doRequest, body);
        doRequest.setHeader("Content-type", "application/json");
        CloseableHttpResponse response = null;
        InputStream in = null;
        String result = null;
        try {
            response = getHttpClient(url).execute(doRequest, HttpClientContext.create());
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                result = IOUtils.toString(in, "utf-8");
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        } finally {
            try{
                if (in != null) {
                    in.close();
                }
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage());
            }
        }
        return result;
    }
    /**
     * 关闭连接池
     */
    private static void closeConnectionPool(){
        try {
            httpClient.close();
            manager.close();
            monitorExecutor.shutdown();
        } catch (IOException e) {
            logger.error(e.getMessage());
        }
    }
}
